package com.lqk.netty.handler.chat;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;

/**
 * @author liqiankun@nnuo.com
 * @date 2023/3/30 19:32
 * @description
 * 1. 我们自定义一个 Handler 需要继承 netty 规定好的某个 HandlerAdapter
 * 2. 这时我们自定义的 Handler 才能称为一个 Handler
 **/
@Slf4j
public class ChatNettyServerHandler extends ChannelInboundHandlerAdapter {
    /**
     * 私聊
     * 使用一个 hashmap 管理 channel
     * 为了实现私聊功能，这里key存储用户的唯一标识，
     * 我保存 客户端的端口号
     * 当然 这个集合也需要自己去维护 用户的上下线 不能像 ChannelGroup那样自己去维护
     */
    public static Map<String,Channel> channelMap = new HashMap<String, Channel>();
    /**
     * 定义一个 channel 组，管理所有的 channel
     * GlobalEventExecutor.INSTANCE 是一个全局的事件执行器，是一个单例
     */
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    /**
     * 时间日期格式化器
     */
    DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    /**
     * handlerAdded 表示连接建立，一旦连接，第一个被执行
     * 将当前 channel 加入到 channelGroup
     * @param ctx 上下文对象，含有 管道 pipline，通道 channel，地址
     * @throws Exception 抛出异常
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // 拿到当前的 channel
        Channel channel = ctx.channel();
        /*
          将该客户加入聊天的信息推送给其他在线的客户端
          channelGroup.writeAndFlush : 该方法会将 channelGroup 中的所有 channel 遍历，并发送消息
          我们不需要自己遍历
         */
        channelGroup.writeAndFlush(df.format(LocalDateTime.now())+"[客户端]: " + channel.remoteAddress() + " 加入聊天\n");
        channelGroup.add(channel);
        // 将所有的客户端端口号存入 channelMap 中
        String key = channel.remoteAddress().toString().split(":")[1];
        channelMap.put(key, channel);
    }
    /**
     * handlerRemoved 表示 断开连接, 将 xx 客户离线信息推送给当前在线的客户端，同时当前 channel 也会自动从 ChannelGroup 中被移除
     * @param ctx 上下文对象，含有 管道 pipline，通道 channel，地址
     * @throws Exception 抛出异常
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.writeAndFlush(df.format(LocalDateTime.now())+"[客户端]" + channel.remoteAddress() + "离开了\n");
        //下线移除
        String key = ctx.channel().remoteAddress().toString().split(":")[1];
        channelMap.remove(key);
        log.info("channelGroup size: " + channelGroup.size());
    }
    /**
     * channelActive 表示 channel 处于活动的状态，可以再次提示 xx 上线了
     * @param ctx 上下文对象，含有 管道 pipline，通道 channel，地址
     * @throws Exception 抛出异常
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("["+df.format(LocalDateTime.now())+"] "+ctx.channel().remoteAddress() + " 上线了~");
    }
    /**
     * channelInactive 表示 channel 处于不活动状态， 提示 xx 下线了
     * @param ctx 上下文对象，含有 管道 pipline，通道 channel，地址
     * @throws Exception 抛出异常
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //下线移除
//        String key = ctx.channel().remoteAddress().toString().split(":")[1];
//        channelMap.remove(key);
        log.info("["+df.format(LocalDateTime.now())+"] "+ctx.channel().remoteAddress() + " 离线了~");
    }
    /**
     * 读取数据的事件（读取客户端发送的消息）
     * @param ctx 上下文对象，含有 管道 pipline，通道 channel，地址
     * @param msg 就是客户端发送的数据，默认是 Object
     * @throws Exception 抛出异常
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 获取到当前 channel
        Channel channel = ctx.channel();
        /**
         * 这里简单判断 如果内容里边包含#那么就是私聊
         */
        String message = (String)msg;
        if(message.contains("#")) {
            String id = message.split("#")[1];
            String body = message.split("#")[2];
            Channel userChannel = channelMap.get(id);
            userChannel.writeAndFlush("["+df.format(LocalDateTime.now())+"] " + channel.remoteAddress() +  body + "\n");
        }else {
            // 这是我们遍历 channelGroup，根据不同的情况，推送不同的消息
            // 使用流的方式进行处理，直接过滤掉自己进行发送
            channelGroup.stream()
                    .filter(ch -> channel != ch)
                    .forEach(ch -> ch.writeAndFlush("["+df.format(LocalDateTime.now())+"] "+ channel.remoteAddress() + msg + "\n"));
        }
    }
    /**
     * 数据读取完毕
     * @param ctx 上下文对象，含有 管道 pipline，通道 channel，地址
     * @throws Exception 抛出异常
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // writeAndFlush 是 write + flush
        // 将数据写入到缓冲并刷新
        // 一般来讲，我们对这个发送的数据进行编码
        ctx.flush();
    }
    /**
     * 处理异常，一般是需要关闭通道
     * @param ctx 上下文对象，含有 管道 pipline，通道 channel，地址
     * @param cause 异常
     * @throws Exception 抛出异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();
        ctx.close();
    }
}

